Skip to content

Conversation

@laileni-aws
Copy link
Contributor

@laileni-aws laileni-aws commented Mar 25, 2025

Problem

Solution

  • Adding UX improvements and agent loop mechanism to Q chat.
QChatPOCFileWrite.mov

TODO:


  • Treat all work as PUBLIC. Private feature/x branches will not be squash-merged at release time.
  • Your code changes must meet the guidelines in CONTRIBUTING.md.
  • License: I confirm that my contribution is made under the terms of the Apache 2.0 license.

@github-actions
Copy link

  • This pull request modifies code in src/* but no tests were added/updated.
    • Confirm whether tests should be added or ensure the PR description explains why tests are not required.
  • This pull request implements a feat or fix, so it must include a changelog entry (unless the fix is for an unreleased feature). Review the changelog guidelines.
    • Note: beta or "experiment" features that have active users should announce fixes in the changelog.
    • If this is not a feature or fix, use an appropriate type from the title guidelines. For example, telemetry-only changes should use the telemetry type.

@laileni-aws laileni-aws marked this pull request as ready for review March 25, 2025 00:53
@laileni-aws laileni-aws requested a review from a team as a code owner March 25, 2025 00:53
@ctlai95
Copy link
Contributor

ctlai95 commented Mar 25, 2025

Can you please update the title to be more specific to this change?

@laileni-aws laileni-aws changed the title feat(amazonq): Agentic chat UX feat(amazonq): Adding openDiff and AcceptDiff UX to the agentic chat. Mar 25, 2025
@laileni-aws laileni-aws changed the title feat(amazonq): Adding openDiff and AcceptDiff UX to the agentic chat. feat(amazonq): Adding openDiff and acceptDiff UX to the agentic chat. Mar 25, 2025
Comment on lines +267 to +303
if (!this.onChatAnswerUpdated || !['accept-code-diff', 'reject-code-diff'].includes(action.id)) {
return
}
const answer: ChatItem = {
type: ChatItemType.ANSWER,
messageId: messageId,
buttons: [],
}
switch (action.id) {
case 'accept-code-diff':
answer.buttons = [
{
keepCardAfterClick: true,
text: 'Accepted code',
id: 'accepted-code-diff',
status: 'success',
position: 'outside',
disabled: true,
},
]
break
case 'reject-code-diff':
answer.buttons = [
{
keepCardAfterClick: true,
text: 'Rejected code',
id: 'rejected-code-diff',
status: 'error',
position: 'outside',
disabled: true,
},
]
break
default:
break
}
this.onChatAnswerUpdated(tabId, answer)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this shouldn't live in this function. Is there somewhere else we can move this to?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is existing way how the buttons work/setup in both VSC and JB.
We can create a function and return answer but basically the functionality does not change.

Copy link
Contributor

@ctlai95 ctlai95 Mar 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this logic is specific to diffs which I don't think belongs here. Why not create a separate diff clicked handler? I see we already have some custom logic in

if (action.id === `open-settings`) {
can't we just add to that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic is not specific to diff's we can extend this to feedback button and even for Confirm button!
Here we show a UX to disable the button once user click on this button.

fileTreeTitle: '',
filePaths: item.contextList.map((file) => file.relativeFilePath),
rootFolderTitle: 'Context',
rootFolderTitle: item.title,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe fallback to ?? 'Context'

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Context is not a generic text across Q so, I feel this should be either undefined or we can supply proper text as part of title which we do in our changes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, the reason I'm suggesting this is because this code is shared with all agents. So unless we are sure that we always pass "Context" in the title for all messages everywhere, there might be a UI regression.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, right now only Q chat uses Context or contextList in project falcon but going forward we need to pass title from the agent controller.ts instead of static text.

onFileClick = (tabID: string, filePath: string, messageId?: string) => {
this.sendMessageToExtension({
command: 'file-click',
command: messageId === '' ? 'file-click' : 'open-diff',
Copy link
Contributor

@ctlai95 ctlai95 Mar 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels a bit hacky. Can there be a separate onOpenDiff handler?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There used to be onOpenDiff function previously but this got deprecated

@deprecated since version 4.6.3. Will be dropped after version 5.x.x. Use {@link onFileClick} instead

So Right now if user click on any file within fileTreeDiff Mynah sends event through onFileClick.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to think about this more. It's not clear to me why if messageId is empty that it should be file-click and not open-diff

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, basically the question is, How can we differentiate the ContextList vs FileListTree ?
image


onCustomFormAction(
tabId: string,
messageId: string,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be string | undefined? Then you don't need to fallback to ''

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This wont be undefined and this is for onCustomFormAction not for onFileClick. Can you elaborate more on this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I didn't understand, aren't you passing in an empty string in connector.ts?

                    this.cwChatConnector.onCustomFormAction(tabId, messageId ?? '', action)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohh I got it,
I am passing '' as part of messageId because of this code

I tested passing undefined and got undesirable result. So, I am passing ''
I remember testing this for RIV for /test too but I will give a shot once again after UX integration.

}
}

export interface FileList {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we use the type from ChatItemContent?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah actually we can use ChatItemContent['fileList']

if (filePath) {
this._listOfReadFiles.push(filePath)
} else {
this._listOfReadFiles = []
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this reset the list? Looks like it would behave unexpectedly if I just look at the name of the function and not the implementation

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If user tries to ask any other question in between generation or if user gets any error we need to reset to files read
To show proper UX of
2 files read, reading Sample.java
3 files read, reading foo.java

etc..

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure but that shouldn't happen here. Someone reading the code in the future is not going to know why it's being reset

Copy link
Contributor Author

@laileni-aws laileni-aws Mar 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me write a separate method(clearListOfReadFiles()) to reset the files.

public get listOfReadFiles(): string[] {
return this._listOfReadFiles
}
public get getFilePath(): string | undefined {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already a getter function, get in the name is redundant

Suggested change
public get getFilePath(): string | undefined {
public get filePath(): string | undefined {


export class ChatSession {
private sessionId?: string
private _listOfReadFiles: string[] = []
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

list is redundant

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Required for the Reading files UX which still a TODO. I can revisit on this once we have final UX into place.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant that the variable name can just be _readFiles

Comment on lines 16 to 18
private _listOfReadFiles: string[] = []
private _filePath: string | undefined
private _tempFilePath: string | undefined
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please check the naming or add some documentation, it's very unclear what these are used for

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will add some comments on top of this variable. There might be some duplication due to lot of dependencies on our PR's.
I will add a TODO to revisit and remove necessary variables if required.

this.sessionId = id
}
public get listOfReadFiles(): string[] {
return this._listOfReadFiles
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I think we can get this information from chatHistory?

Copy link
Contributor Author

@laileni-aws laileni-aws Mar 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please can you point me the code where this was implemented? I checked the latest PR and looks like its still TODO

@laileni-aws laileni-aws requested a review from ctlai95 March 25, 2025 20:44
public setSessionID(id?: string) {
this.sessionId = id
}
public get listOfReadFiles(): string[] {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public get listOfReadFiles(): string[] {
public get readFiles(): string[] {

Copy link

@mr-lee mr-lee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved to unblock.

Comment on lines +403 to +407
const fileExists = await fs.existsFile(filePath)
// Check if fileExists=false, If yes, return instead of showing broken diff experience.
if (!session.tempFilePath) {
return
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this notify the user?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No It wont notify the user for now, but good to have a Information notification message. I will add this to the TODO.

export class ChatSession {
private sessionId?: string
/**
* _listOfReadFiles = list of files read from the project to gather context before generating response.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do you need to pass this around?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To count # of files read as per UX requirement.

@mr-lee mr-lee merged commit 8392a4c into aws:feature/agentic-chat Mar 25, 2025
15 of 17 checks passed
@laileni-aws laileni-aws deleted the part1 branch April 21, 2025 03:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants